package com.inspectortime.util;

import java.net.InetAddress;
import java.net.UnknownHostException;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

/**
 * Encodes a string using a shared secret and ip/hostname of local computer.
 * This means that secrets can be passed among java apps (like webapps)
 * running on the same local computer
 * 
 * @param token
 * @return
 */
public class DesLocalMachineEncrypter {

	private static byte[] SECRET = new byte[] {16, -111, 109, 35};
	
	private Cipher ecipher;
    private Cipher dcipher;
	
	private static byte[] generateKeyFromSecretAndIp() {
		String ip;
		try {
			ip = InetAddress.getLocalHost().getHostAddress();
		} catch (UnknownHostException e) {
			throw new RuntimeException(e);
		}
		byte[] ipBytes = ip.getBytes();
		byte[] key7Bytes = new byte[7];
		key7Bytes[0] = SECRET[0];
		key7Bytes[1] = SECRET[1];
		key7Bytes[2] = SECRET[2];
		key7Bytes[3] = SECRET[3];
		key7Bytes[4] = ipBytes[ipBytes.length-1];
		key7Bytes[5] = ipBytes[ipBytes.length-2];
		key7Bytes[6] = ipBytes[ipBytes.length-3];	
		byte[] key8BytesWithParity = addParity(key7Bytes);
		return key8BytesWithParity;		
	}
	
	public DesLocalMachineEncrypter() {
		byte[] keyBytes = generateKeyFromSecretAndIp();
		verifyKey(keyBytes);
		SecretKey key = new SecretKeySpec(keyBytes, "DES");
        try {
            ecipher = Cipher.getInstance("DES");
            dcipher = Cipher.getInstance("DES");
            ecipher.init(Cipher.ENCRYPT_MODE, key);
            dcipher.init(Cipher.DECRYPT_MODE, key);
        } catch (Exception e) {
        	throw new RuntimeException(e);
        } 
	}
	
	// Takes a 7-byte quantity and returns a valid 8-byte DES key.
    // The input and output bytes are big-endian, where the most significant
    // byte is in element 0.
    private static byte[] addParity(byte[] in) {
        byte[] result = new byte[8];
    
        // Keeps track of the bit position in the result
        int resultIx = 1;
    
        // Used to keep track of the number of 1 bits in each 7-bit chunk
        int bitCount = 0;
    
        // Process each of the 56 bits
        for (int i=0; i<56; i++) {
            // Get the bit at bit position i
            boolean bit = (in[6-i/8]&(1<<(i%8))) > 0;
    
            // If set, set the corresponding bit in the result
            if (bit) {
                result[7-resultIx/8] |= (1<<(resultIx%8))&0xFF;
                bitCount++;
            }
    
            // Set the parity bit after every 7 bits
            if ((i+1) % 7 == 0) {
                if (bitCount % 2 == 0) {
                    // Set low-order bit (parity bit) if bit count is even
                    result[7-resultIx/8] |= 1;
                }
                resultIx++;
                bitCount = 0;
            }
            resultIx++;
        }
        return result;
    }

	
    /**
     * checks that the parity has been set properly
     * @see http://www.exampledepot.com/egs/javax.crypto/MakeDes.html?l=rel
     * @param keyBytes
     * @return
     */
    private static void verifyKey(byte[] keyBytes) {
        try {
            boolean b = DESKeySpec.isParityAdjusted(keyBytes, 0);
            if (!b) {
            	throw new RuntimeException("Invalid DES key");
            }
        } catch (java.security.InvalidKeyException e) {
        	throw new RuntimeException(e);
        }    	
    }
    
    public String encrypt(String str) {
        try {
            // Encode the string into bytes using utf-8
            byte[] utf8 = str.getBytes("UTF8");

            // Encrypt
            byte[] enc = ecipher.doFinal(utf8);

            // Encode bytes to base64 to get a string
            return new String(Base64.encodeBase64(enc)); 
            // Fails in java6 -- return new sun.misc.BASE64Encoder().encode(enc);
        } catch (Exception e) {
        	throw new RuntimeException(e);
        }
    }

    public String decrypt(String str) {
        try {
            // Decode base64 to get bytes
            // fails in java6 -- byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
        	byte[] dec = Base64.decodeBase64(str.getBytes());

            // Decrypt
            byte[] utf8 = dcipher.doFinal(dec);

            // Decode using utf-8
            return new String(utf8, "UTF8");
        } catch (Exception e) {
        	throw new RuntimeException(e);
        }        
    }
}
